/* test_progcache.c contains single-threaded correctness tests for
   progcache. */

#include "test_progcache_common.c"
#include "../runtime/fd_system_ids.h"
#include <stdlib.h>
#include <regex.h>

FD_IMPORT_BINARY( valid_program_data,        "src/ballet/sbpf/fixtures/hello_solana_program.so" );
FD_IMPORT_BINARY( bigger_valid_program_data, "src/ballet/sbpf/fixtures/clock_sysvar_program.so" );
FD_IMPORT_BINARY( invalid_program_data,      "src/ballet/sbpf/fixtures/malformed_bytecode.so"   );

/* query_rec_exact fetches a funk record at a precise xid:key pair. */

static fd_funk_rec_t const *
query_rec_exact( test_env_t *              env,
                 fd_funk_txn_xid_t const * xid,
                 fd_funk_rec_key_t const * key ) {
  fd_funk_xid_key_pair_t pair[1];
  fd_funk_txn_xid_copy( pair->xid, xid );
  fd_funk_rec_key_copy( pair->key, key );

  fd_funk_rec_map_query_t query[1];
  int query_err = fd_funk_rec_map_query_try( env->progcache->funk->rec_map, pair, NULL, query, 0 );
  if( query_err==FD_MAP_ERR_KEY ) return NULL;
  if( FD_UNLIKELY( query_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_funk_rec_map_query_try failed: %i-%s", query_err, fd_map_strerror( query_err ) ));

  return fd_funk_rec_map_query_ele_const( query );
}

/* test_empty: Account database and progcache completely empty.
   Query at root should fail. */

static void
test_empty( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );

  fd_funk_txn_xid_t xid[1]; fd_funk_txn_xid_set_root( xid );
  fd_funk_rec_key_t key = test_key( 1UL );
  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, xid, &key, &load_env );
  FD_TEST( !rec );

  test_env_destroy( env );
}

/* test_account_does_not_exist: Program account missing, but querying at
   a fork. */

static void
test_account_does_not_exist( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  (void)test_env_txn_publish;

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalid_owner: Account exists but is not owned by BPF loader */

static void
test_invalid_owner( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_system_program_id, /* not a BPF laoder */
                       invalid_program_data,
                       invalid_program_data_sz,
                       1 );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  FD_TEST( !fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env ) );

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalid_program: Program account exists but fails loading */

static void
test_invalid_program( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       invalid_program_data,
                       invalid_program_data_sz,
                       1 );

  FD_TEST( !fd_progcache_peek( env->progcache, &fork_a, &key, 0UL ) );
  FD_TEST( env->progcache->fork_depth==2UL );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 0 ], &fork_a ) );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 1 ], fd_funk_root( env->progcache->funk ) ) );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( !rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, load_env.epoch_slot0 )==rec );

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_valid_program: Load a valid program account */

static void
test_valid_program( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  FD_TEST( !fd_progcache_peek( env->progcache, &fork_a, &key, 0UL ) );
  FD_TEST( env->progcache->fork_depth==2UL );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 0 ], &fork_a ) );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 1 ], fd_funk_root( env->progcache->funk ) ) );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );
  FD_TEST( env->progcache->fork_depth==2UL );

  fd_funk_txn_xid_t fork_b = { .ul = { 64UL, 2UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec );
  FD_TEST( env->progcache->fork_depth==3UL );

  load_env.slot        = 64UL;
  load_env.epoch       =  0UL;
  load_env.epoch_slot0 =  0UL;
  fd_progcache_rec_t const * rec2 = fd_progcache_pull( env->progcache, env->accdb, &fork_b, &key, &load_env );
  FD_TEST( rec==rec2 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec );

  test_env_txn_cancel( env, &fork_a ); /* should also cancel fork_b */
  test_env_destroy( env );
}

/* test_epoch_boundary: Ensure that a valid program gets re-verified
   after an epoch boundary. */

static void
test_epoch_boundary( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  FD_TEST( !fd_progcache_peek( env->progcache, &fork_a, &key, 0UL ) );
  FD_TEST( env->progcache->fork_depth==2UL );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 0 ], &fork_a ) );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 1 ], fd_funk_root( env->progcache->funk ) ) );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  fd_funk_txn_xid_t fork_b = { .ul = { 64UL, 2UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  load_env.slot        = 64UL;
  load_env.epoch       =  1UL;
  load_env.epoch_slot0 = 64UL;
  fd_progcache_rec_t const * rec2 = fd_progcache_pull( env->progcache, env->accdb, &fork_b, &key, &load_env );
  FD_TEST( rec2 );
  FD_TEST( rec!=rec2 );
  FD_TEST( rec2->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, load_env.epoch_slot0 )==rec2 );

  test_env_txn_cancel( env, &fork_b );
  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalidate: Ensure that an fd_progcache_invalidate call
   overrides a previously created cache entry. */

static void
test_invalidate( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  fd_funk_txn_xid_t fork_b = { .ul = { 2UL, 1UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  fd_progcache_rec_t const * rec2 = fd_progcache_invalidate( env->progcache, &fork_b, &key, fork_b.ul[0] );
  FD_TEST( rec2!=rec );
  FD_TEST( !rec2->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec2 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalidate_nonexistent: fd_progcache_invalidate should create an
   entry even if there are no other cache entries for the same key.
   (To prevent a future fill predating the invalidation from having side
   effects for slots after the invalidation.) */

static void
test_invalidate_nonexistent( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  fd_progcache_rec_t const * rec = fd_progcache_invalidate( env->progcache, &fork_a, &key, fork_a.ul[0] );
  FD_TEST( rec );
  FD_TEST( !rec->executable );

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalidate_pull: fd_progcache_pull should recover from an
   earlier fd_progcache_invalidate call (in a future slot). */

static void
test_invalidate_pull( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  /* Create initial cache entry */
  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  /* Create cache invalidation entry */
  fd_funk_txn_xid_t fork_b = { .ul = { 2UL, 1UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  fd_progcache_rec_t const * rec2 = fd_progcache_invalidate( env->progcache, &fork_b, &key, fork_b.ul[0] );
  FD_TEST( rec2!=rec );
  FD_TEST( !rec2->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec2 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  /* Loading the program should create another cache entry */
  fd_funk_txn_xid_t fork_c = { .ul = { 3UL, 2UL } };
  test_env_txn_prepare( env, &fork_b, &fork_c );
  load_env.slot = 3UL;
  fd_progcache_rec_t const * rec3 = fd_progcache_pull( env->progcache, env->accdb, &fork_c, &key, &load_env );
  FD_TEST( rec3 );
  FD_TEST( rec3!=rec2 && rec3!=rec );
  FD_TEST( rec3->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_c, &key, 0UL )==rec3 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec2 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec  );

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalidate_dup: fd_progcache_invalidate should create a cache
   invalidation entry, even if last update was an invalidation.  Because
   a future cache access could create a cache entry between the two
   retro-actively. */

static void
test_invalidate_dup( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  /* Create initial cache entry */
  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  /* Create cache invalidation entry */
  fd_funk_txn_xid_t fork_b = { .ul = { 2UL, 1UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  fd_progcache_rec_t const * rec2 = fd_progcache_invalidate( env->progcache, &fork_b, &key, fork_b.ul[0] );
  FD_TEST( rec2!=rec );
  FD_TEST( !rec2->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec2 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );
  FD_TEST( fd_progcache_invalidate( env->progcache, &fork_b, &key, fork_b.ul[0] )==rec2 );

  /* Create cache invalidation entry */
  fd_funk_txn_xid_t fork_c = { .ul = { 3UL, 2UL } };
  test_env_txn_prepare( env, &fork_b, &fork_c );
  fd_progcache_rec_t const * rec3 = fd_progcache_invalidate( env->progcache, &fork_c, &key, fork_c.ul[0] );
  FD_TEST( rec3 && rec2!=rec3 );
  FD_TEST( !rec3->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_c, &key, 0UL )==rec3 );

  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_invalidate_epoch_boundary: fd_progcache_invalidate after a cache
   entry, even if the program is already invalid (due to an epoch
   boundary).  Because a future cache access could create a cache entry
   between the two retro-actively.  */

static void
test_invalidate_epoch_boundary( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  FD_TEST( !fd_progcache_peek( env->progcache, &fork_a, &key, 0UL ) );
  FD_TEST( env->progcache->fork_depth==2UL );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 0 ], &fork_a ) );
  FD_TEST( fd_funk_txn_xid_eq( &env->progcache->fork[ 1 ], fd_funk_root( env->progcache->funk ) ) );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec );
  FD_TEST( rec->executable );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec );

  fd_funk_txn_xid_t fork_b = { .ul = { 64UL, 2UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  load_env.slot        = 64UL;
  load_env.epoch       =  1UL;
  load_env.epoch_slot0 = 64UL;
  fd_progcache_rec_t const * rec2 = fd_progcache_invalidate( env->progcache, &fork_b, &key, fork_b.ul[0] );
  FD_TEST( rec2 && rec!=rec2 );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, load_env.epoch_slot0 )==rec2 );

  test_env_txn_cancel( env, &fork_b );
  test_env_txn_cancel( env, &fork_a );
  test_env_destroy( env );
}

/* test_publish_gc: fd_progcache_txn_publish should garbage-collect
   stale entries. */

static void
test_publish_gc( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec_a = fd_progcache_pull( env->progcache, env->accdb, &fork_a, &key, &load_env );
  FD_TEST( rec_a );
  FD_TEST( rec_a->executable );

  fd_funk_txn_xid_t fork_b = { .ul = { 2UL, 1UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  fd_progcache_rec_t const * rec_b = fd_progcache_invalidate( env->progcache, &fork_b, &key, fork_b.ul[0] );
  FD_TEST( rec_b );

  fd_funk_txn_xid_t fork_c = { .ul = { 3UL, 2UL } };
  test_env_txn_prepare( env, &fork_b, &fork_c );
  load_env.slot = 3UL;
  fd_progcache_rec_t const * rec_c = fd_progcache_pull( env->progcache, env->accdb, &fork_c, &key, &load_env );
  FD_TEST( rec_c );
  FD_TEST( rec_c->executable );

  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec_a );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_b, &key, 0UL )==rec_b );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_c, &key, 0UL )==rec_c );

  fd_funk_rec_t const * frec_a = query_rec_exact( env, &fork_a, &key );
  fd_funk_rec_t const * frec_b = query_rec_exact( env, &fork_b, &key );
  fd_funk_rec_t const * frec_c = query_rec_exact( env, &fork_c, &key );
  FD_TEST( frec_a ); FD_TEST( frec_b ); FD_TEST( frec_c );
  FD_TEST( frec_a!=frec_b && frec_a!=frec_c && frec_b!=frec_c );

  fd_funk_txn_xid_t root; fd_funk_txn_xid_set_root( &root );
  test_env_txn_publish( env, &fork_a );
  FD_TEST( query_rec_exact( env, &fork_a, &key )==NULL   );
  FD_TEST( query_rec_exact( env, &root,   &key )==frec_a );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==rec_a );

  test_env_txn_publish( env, &fork_b );
  FD_TEST( query_rec_exact( env, &fork_a, &key )==NULL );
  FD_TEST( query_rec_exact( env, &fork_b, &key )==NULL );
  FD_TEST( query_rec_exact( env, &root,   &key )==NULL );  /* invalidation rooted */

  test_env_txn_publish( env, &fork_c );
  FD_TEST( query_rec_exact( env, &fork_a, &key )==NULL   );
  FD_TEST( query_rec_exact( env, &fork_b, &key )==NULL   );
  FD_TEST( query_rec_exact( env, &fork_c, &key )==NULL   );
  FD_TEST( query_rec_exact( env, &root,   &key )==frec_c );

  /* Verify that only frec_c exists in funk rec map */
  ulong chain_idx = fd_funk_rec_map_iter_chain_idx( env->progcache->funk->rec_map, &frec_c->pair );
  ulong chain_cnt = 0UL;
  for( fd_funk_rec_map_iter_t iter = fd_funk_rec_map_iter( env->progcache->funk->rec_map, chain_idx );
       !fd_funk_rec_map_iter_done( iter );
       iter = fd_funk_rec_map_iter_next( iter ) ) {
    chain_cnt++;
  }
  FD_TEST( chain_cnt==1UL );

  test_env_destroy( env );
}

static void
test_publish_gc2( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_a = { .ul = { 1UL, 1UL } };
  test_env_txn_prepare( env, NULL, &fork_a );

  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_a, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );

  fd_prog_load_env_t load_env = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };

  fd_progcache_rec_t const * rec_a = fd_progcache_invalidate( env->progcache, &fork_a, &key, fork_a.ul[0] );
  FD_TEST( rec_a );

  fd_funk_rec_t const * frec_a = query_rec_exact( env, &fork_a, &key ); FD_TEST( frec_a );
  fd_funk_txn_xid_t root; fd_funk_txn_xid_set_root( &root );
  test_env_txn_publish( env, &fork_a );
  FD_TEST( query_rec_exact( env, &fork_a, &key )==NULL );
  FD_TEST( query_rec_exact( env, &root,   &key )==NULL );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_a, &key, 0UL )==NULL );

  fd_funk_txn_xid_t fork_b = { .ul = { 2UL, 1UL } };
  test_env_txn_prepare( env, &fork_a, &fork_b );
  load_env.slot = 2;
  test_env_txn_publish( env, &fork_b );
  FD_TEST( query_rec_exact( env, &fork_a, &key )==NULL );
  FD_TEST( query_rec_exact( env, &fork_b, &key )==NULL );
  FD_TEST( query_rec_exact( env, &root,   &key )==NULL );

  fd_funk_txn_xid_t fork_c = { .ul = { 3UL, 1UL } };
  test_env_txn_prepare( env, &fork_b, &fork_c );
  load_env.slot = 3UL;
  fd_progcache_rec_t const * rec_c = fd_progcache_pull( env->progcache, env->accdb, &fork_c, &key, &load_env );
  FD_TEST( rec_c );
  FD_TEST( rec_c->executable );
  test_env_txn_publish( env, &fork_c );
  FD_TEST( fd_progcache_peek( env->progcache, &fork_c, &key, 0UL )==rec_c );

  fd_funk_txn_xid_t fork_d = { .ul = { 4UL, 1UL } };
  test_env_txn_prepare( env, &fork_c, &fork_d );
  load_env.slot = 4UL;
  fd_progcache_rec_t const * rec_d = fd_progcache_pull( env->progcache, env->accdb, &fork_d, &key, &load_env );
  FD_TEST( rec_d );
  FD_TEST( rec_d->executable );
  test_env_txn_publish( env, &fork_d );

  fd_funk_txn_xid_t fork_e = { .ul = { 5UL, 1UL } };
  test_env_txn_prepare( env, &fork_d, &fork_e );
  load_env.slot = 5UL;
  fd_progcache_rec_t const * rec_e = fd_progcache_invalidate( env->progcache, &fork_e, &key, fork_e.ul[0] );
  FD_TEST( rec_e );
  test_env_txn_publish( env, &fork_e );

  fd_funk_txn_xid_t fork_f = { .ul = { 6UL, 1UL } };
  test_env_txn_prepare( env, &fork_e, &fork_f );
  load_env.slot = 6UL;
  fd_progcache_rec_t const * rec_f = fd_progcache_pull( env->progcache, env->accdb, &fork_f, &key, &load_env );
  FD_TEST( rec_f );
  test_env_txn_publish( env, &fork_f );

  /* Verify that only one record remains */
  fd_funk_xid_key_pair_t pair[1];
  fd_funk_xid_key_pair_init( pair, &root, &key );
  ulong chain_idx = fd_funk_rec_map_iter_chain_idx( env->progcache->funk->rec_map, pair );
  ulong chain_cnt = 0UL;
  for( fd_funk_rec_map_iter_t iter = fd_funk_rec_map_iter( env->progcache->funk->rec_map, chain_idx );
       !fd_funk_rec_map_iter_done( iter );
       iter = fd_funk_rec_map_iter_next( iter ) ) {
    chain_cnt++;
  }
  FD_TEST( chain_cnt==1UL );

  test_env_destroy( env );
}

static void
test_publish_trivial( fd_wksp_t * wksp ) {
  /* Exercise a sequence of prepare/publish operations seen when running
     'firedancer-dev backtest' */

  test_env_t * env = test_env_create( wksp );

  fd_funk_txn_xid_t root; fd_funk_txn_xid_set_root( &root );
  fd_funk_txn_xid_t fork_368528500 = { .ul = { 368528500UL, 368528500UL } };
  fd_progcache_txn_attach_child( env->progcache_admin, &root, &fork_368528500 );
  fd_progcache_txn_advance_root( env->progcache_admin,        &fork_368528500 );

  /* FIXME more operations here ... */
}

/* test_root_nonroot_prio: non-rooted record should take priority over
   rooted records. */

static void
test_root_nonroot_prio( fd_wksp_t * wksp ) {
  test_env_t * env = test_env_create( wksp );
  fd_funk_txn_xid_t fork_1 = { .ul = { 1UL, 1UL } }; /* account deployed here */
  fd_funk_txn_xid_t fork_2 = { .ul = { 2UL, 1UL } }; /* root */
  fd_funk_txn_xid_t fork_3 = { .ul = { 3UL, 2UL } }; /* account redeployed here */
  fd_funk_txn_xid_t fork_4 = { .ul = { 4UL, 2UL } }; /* tip */

  test_env_txn_prepare( env, NULL, &fork_1 );
  fd_funk_rec_key_t key = test_key( 1UL );
  create_test_account( env, &fork_1, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );
  test_env_txn_publish( env, &fork_1 );

  test_env_txn_prepare( env, &fork_1, &fork_2 );
  test_env_txn_prepare( env, &fork_2, &fork_3 );
  create_test_account( env, &fork_3, &key,
                       &fd_solana_bpf_loader_program_id,
                       valid_program_data,
                       valid_program_data_sz,
                       1 );
  fd_progcache_invalidate( env->progcache, &fork_3, &key, 3UL );
  test_env_txn_prepare( env, &fork_3, &fork_4 );

  fd_prog_load_env_t load_env1 = {
    .features    = env->features,
    .slot        = 1UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec1 = fd_progcache_pull( env->progcache, env->accdb, &fork_1, &key, &load_env1 );
  FD_TEST( rec1 );
  FD_TEST( rec1->slot==1UL );

  fd_prog_load_env_t load_env4 = {
    .features    = env->features,
    .slot        = 4UL,
    .epoch       = 0UL,
    .epoch_slot0 = 0UL
  };
  fd_progcache_rec_t const * rec4 = fd_progcache_pull( env->progcache, env->accdb, &fork_4, &key, &load_env4 );
  FD_TEST( rec4 );
  FD_TEST( rec4->slot==4UL );

  test_env_txn_cancel( env, &fork_4 );
  test_env_txn_cancel( env, &fork_3 );
  test_env_destroy( env );
}

struct test_case {
  char const * name;
  void      (* fn)( fd_wksp_t * wksp );
};

static int
match_test_name( char const * test_name,
                 int          argc,
                 char **      argv ) {
  if( argc<=1 ) return 1;
  for( int i=1; i<argc; i++ ) {
    if( argv[ i ][ strspn( argv[ i ], " \t\n\r" ) ]=='\0' ) continue;
    if( strstr( test_name, argv[ i ] ) ) return 1;
  }
  return 0;
}

int
main( int     argc,
      char ** argv ) {
  fd_boot( &argc, &argv );

  ulong cpu_idx = fd_tile_cpu_id( fd_tile_idx() );
  if( cpu_idx>fd_shmem_cpu_cnt() ) cpu_idx = 0UL;

  char const * _page_sz  = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz",   NULL, "gigantic"                   );
  ulong        page_cnt  = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt",  NULL, 2UL                          );
  ulong        numa_idx  = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx",  NULL, fd_shmem_numa_idx( cpu_idx ) );

  ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz );
  if( FD_UNLIKELY( !page_sz ) ) FD_LOG_ERR(( "unsupported --page-sz" ));

  FD_LOG_NOTICE(( "Creating workspace (--page-cnt %lu, --page-sz %s, --numa-idx %lu)", page_cnt, _page_sz, numa_idx ));
  fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL );
  FD_TEST( wksp );

# define TEST( name ) { #name, name }
  struct test_case cases[] = {
    TEST( test_empty ),
    TEST( test_account_does_not_exist ),
    TEST( test_invalid_owner ),
    TEST( test_invalid_program ),
    TEST( test_valid_program ),
    TEST( test_epoch_boundary ),
    TEST( test_invalidate ),
    TEST( test_invalidate_nonexistent ),
    TEST( test_invalidate_pull ),
    TEST( test_invalidate_dup ),
    TEST( test_invalidate_epoch_boundary ),
    TEST( test_publish_gc ),
    TEST( test_publish_gc2 ),
    TEST( test_publish_trivial ),
    TEST( test_root_nonroot_prio ),
    {0}
  };
# undef TEST
  for( struct test_case * tc = cases; tc->name; tc++ ) {
    if( match_test_name( tc->name, argc, argv ) ) {
      FD_LOG_NOTICE(( "Running %s", tc->name ));
      tc->fn( wksp );
    }
  }

  fd_wksp_delete_anonymous( wksp );

  FD_LOG_NOTICE(( "pass" ));
  fd_halt();
  return 0;
}
